/*
Minims .Net Playstation 3 UPNP ContentDirectory
Copyright (C) 2008  Guillaume Plourde

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; version 2
of the License.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

using System;
using System.Collections.Generic;
using System.Text;

using System.Xml;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using System.IO;

namespace minims
{
	public class SSDP_SERVER
	{
		const String UPNP_GROUP = "239.255.255.250";
		const int UPNP_PORT = 1900;

		private bool Started;

		private IPAddress ipaddress;
		private Socket s_send;
		private Socket s_receive;
		private Socket s_notify;

		private Thread ssdpThread;
		private Thread notifyThread;

		public SSDP_SERVER()
		{
			ipaddress = IPAddress.Parse(UPNP_GROUP);

			s_send = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

			s_notify = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
			s_notify.Connect(new IPEndPoint(ipaddress, UPNP_PORT));
			s_notify.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(ipaddress));

			s_receive = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
			s_receive.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);
			s_receive.Bind(new IPEndPoint(IPAddress.Any, UPNP_PORT));
			s_receive.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(ipaddress));
		}
		public void Start()
		{
			Started = true;

			ssdpThread = new Thread(Receive);
			ssdpThread.Start();

			notifyThread = new Thread(Notify);
			notifyThread.Start();
		}
		public void Stop()
		{
			NotifyByeBye();
			ssdpThread.Abort();
			Started = false;
		}
		public void Receive()
		{
			while (Started)
			{
				if (s_receive.Available > 0)
				{
					byte[] buffer = new byte[64000];
					MemoryStream ms = new MemoryStream();

					IPEndPoint remoteip = new IPEndPoint(IPAddress.Any, 0);
					EndPoint remote = (EndPoint)remoteip;
					int recv = s_receive.ReceiveFrom(buffer, s_receive.Available, SocketFlags.None, ref remote);
					ms.Write(buffer, 0, recv);

					StreamReader reader = new StreamReader(ms, Encoding.UTF8);

					ms.Position = 0;

					String request = reader.ReadLine();

					if (request.StartsWith("M-SEARCH"))
					{
						String line = reader.ReadToEnd();

						String[] separator = { "\r\n" };
						String[] args = line.Split(separator, StringSplitOptions.RemoveEmptyEntries);

						List<String> headers = new List<string>();
						List<String> body = new List<string>();

						foreach (string s in args)
						{
							if (s.Length < 1)
								continue;

							String l = s.Replace(": ", ":");
							String[] sep = { ":" };
							String[] headargs = l.Split(sep, 2, StringSplitOptions.RemoveEmptyEntries);

							headers.Add(headargs[0]);
							body.Add(headargs[1]);
						}
						SearchReply(remote);
					}
				}
			}
		}
		public void Notify()
		{
			while (Started)
			{
				//Console.WriteLine("Notify packet sent...");

				s_notify.SendTo(Build_NotifyPacket(ConfigManager.GetUniqueDeviceName, "::upnp:rootdevice", "upnp:rootdevice", "", "alive"), s_notify.RemoteEndPoint);
				s_notify.SendTo(Build_NotifyPacket(ConfigManager.GetUniqueDeviceName, "::urn:schemas-upnp-org:device:ContentDirectory:1", "urn:schemas-upnp-org:device:ContentDirectory:1", "", "alive"), s_notify.RemoteEndPoint);
				Thread.Sleep(30000);
			}
		}
		public void NotifyByeBye()
		{
			//Console.WriteLine("ByeBye Notify packet sent...");

			s_notify.SendTo(Build_NotifyPacket(ConfigManager.GetUniqueDeviceName, "::upnp:rootdevice", "upnp:rootdevice", "", "byebye"), s_notify.RemoteEndPoint);
			s_notify.SendTo(Build_NotifyPacket(ConfigManager.GetUniqueDeviceName, "::urn:schemas-upnp-org:device:ContentDirectory:1", "urn:schemas-upnp-org:device:ContentDirectory:1", "", "byebye"), s_notify.RemoteEndPoint);
		}
		public void SearchReply(EndPoint remote)
		{
			//Console.WriteLine("Search reply packet sent...");
			Random random = new Random();

			s_send.SendTo(Build_SsdpSearchPacket(ConfigManager.GetUniqueDeviceName, "::upnp:rootdevice", "upnp:rootdevice", ""), remote);
			s_send.SendTo(Build_SsdpSearchPacket(ConfigManager.GetUniqueDeviceName, "::urn:schemas-upnp-org:device:ContentDirectory:1", "urn:schemas-upnp-org:device:ContentDirectory:1", ""), remote);
		}

		Byte[] Build_NotifyPacket(String USN, String USNex, String ST, String NTex, String NTs)
		{
			String packet = "NOTIFY * HTTP/1.1\r\n"
			+ "LOCATION: http://" + ConfigManager.GetHostIp + ":" + ConfigManager.GetPort + "/System/Root.xml\r\n"
			+ "HOST: 239.255.255.250:1900\r\n"
			+ "SERVER: WINDOWS, UPnP/1.0, " + ConfigManager.GetFriendlyName + "/1.0\r\n"
			+ "NTS: ssdp:" + NTs + "\r\n"
			+ "USN: uuid:" + USN + USNex + "\r\n"
			+ "CACHE-CONTROL: max-age=1800\r\n"
			+ "NT: " + ST + NTex + "\r\n"
			+ "\r\n";
			return Encoding.UTF8.GetBytes(packet);
		}
		Byte[] Build_SsdpSearchPacket(String USN, String USNex, String ST, String NTex	)
		{
			String packet = "HTTP/1.1 200 OK\r\n"
			+ "Ext:\r\n"
			+ "Date: " + DateTime.Now.ToLongDateString() + " " + DateTime.Now.ToLongTimeString() + "\r\n"
			+ "LOCATION: http://" + ConfigManager.GetHostIp + ":" + ConfigManager.GetPort + "/System/Root.xml\r\n"
			+ "SERVER: WINDOWS, UPnP/1.0, Minims .Net/1.0\r\n"
			+ "USN: uuid:" + USN + USNex + "\r\n"
			+ "CACHE-CONTROL: max-age=1800\r\n"
			+ "ST: " + ST + NTex + "\r\n"
			+ "Content-Length: 0\r\n"
			+ "\r\n";
			return Encoding.UTF8.GetBytes(packet);
		}
	}
}